#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File  : st.py
# @Author: shadow
# @Date  : 2020/3/3
# @Desc  :

# !/usr/bin/env python
# -*- coding: utf-8 -*-
# @File  : studentSystem.py
# @Author: shadow
# @Date  : 2020/3/3
# @Desc  :

"""
编写一个学生管理系统类 ---Gradebook:
要求能动态的添加学生-name和学生成绩 grades(一个学生可以有个多科的科目成绩)，并且根据学生名字能算出学生的平均成绩：
提示：可以预习初始化一个字典，该实例字典存储相应的信息
1 编写一个表示科目的类--Subject，该类包含一系列的考试成绩
2.编写一个表示学生的类--Student， 该类包含此学生正在学习的各项课程
3.编写一个包含所有学生考试成绩的容器类--Gradebook，以学生的名字为key，并且可以动态的添加学生
"""
import inspect

subjects = ("chinese", "math", "english")


# 科目类：记录科目成绩，作为数据描述器
class Subject:
    def __init__(self, subject, score):
        self.subject = subject  # 科目
        self.score = score  # 分数

    def __repr__(self):
        return f"{self.subject}:{self.score}"

    def __set__(self, instance, value):
        if isinstance(value, int):
            instance.__dict__[self.subject] = value
        else:
            raise TypeError

    # def __get__(self, instance, owner):
    #     if instance:
    #         return instance.__dict__[self.subject]
    #     else:
    #         return self


# 参数判断，注入, 并作为装饰器使用
class ParamInject:
    def __init__(self, cls):
        self.cls = cls  # Student
        sig = inspect.signature(cls)
        for name, para in sig.parameters.items():   # 获取类的初始化参数
            # print(name,  para.name, para.kind, para.default, para.annotation)
            if para.annotation == para.empty:   # 没有给类型
                pass
            elif para.annotation == int:
                setattr(cls, name, Subject(name, para.default))  # 动态添加属性
            else:   # 其他类型
                pass

    def __call__(self, *args, **kwargs):
        return self.cls(*args, **kwargs)    # 构建一个对象，以便装饰器使用


# 学生类：记录单个学生的各科成绩， 使用类装饰器，自动获取参数生成描述器
@ParamInject    # ParamInject(Student)
class Student:
    # chinese = Subject("chinese", 0)
    # math = Subject("math", 0)
    # english = Subject("english", 0)
    def __init__(self, chinese: int = 0, math: int = 0, english: int = 0):
        self.chinese = chinese
        self.math = math
        self.english = english
        self.sum = self.chinese + self.math + self.english
        self.avg = round(self.sum/3, 1)

    def get_sum(self):  # 总分
        self.sum = sum((self.chinese, self.math, self.english))
        return self.sum

    def get_avg(self):   # 获取平均分, 保留一位小数
        self.avg = round(self.get_sum()/3, 1)
        return self.avg

    def set_score(self, subject: str, score):  # 修改分数
        """
        :param subject: 科目名称，str
        :param score: 分数，int
        :return:
        """
        if not isinstance(score, int):
            raise TypeError
        if subject in subjects:
            setattr(self, subject, score)

    def __repr__(self):  # 输出学生各科成绩和平均分
        return f"Chinese:{self.chinese:3}, Math:{self.math:3} English:{self.english:3} avg: {self.get_avg()}"


default = Student()    # 生成一个默认对象供比较类型


# 考试成绩类，记录学生考试信息
class Gradebook:
    def __init__(self):
        self.gradeBook = {}
        self.name = []  # 记录学生名单
        self._chinese = []  # 记录所有学生中文成绩
        self._math = []  # 记录所有学生数学成绩
        self._english = []  # 记录所有学生英语成绩

    # 获取单科所有成绩前先刷新一遍
    @property
    def chinese(self):
        self.update(True)
        return "Chinese", dict(zip(self.name, self._chinese))

    @property
    def math(self):
        self.update(True)
        return "Math", dict(zip(self.name, self._math))

    @property
    def english(self):
        self.update(True)
        return "English", dict(zip(self.name, self._english))

    def __enter__(self):
        yield from self.name

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")

    def __len__(self):
        return len(self.name)

    def __getitem__(self, item):
        try:
            return self.gradeBook[item]
        except:
            raise IndexError("No this student!")

    def __setitem__(self, name, value):
        # if isinstance(value, Student):  # 类型问题，使用Student是装饰器的类型
        if name in self.name:
            raise NameError("Name already exists!")
        if isinstance(value, type(default)):
            self.gradeBook[name] = value
            self.name.append(name)
            self.update(False, value)
        else:
            raise TypeError("Not class Student!")

    def update(self, all=False, student:Student=default):   # 通过student对象修改分数后不能同步更新，需要手动刷新
        if all:    # TODO 遍历更新分数，效率低
            for name, info in self.gradeBook.items():
                index = self.name.index(name)
                self._chinese[index] = info.chinese
                self._math[index] = info.math
                self._english[index] = info.english
        else:   # 添加学生
            self._chinese.append(student.chinese)
            self._math.append(student.math)
            self._english.append(student.english)

    def __delitem__(self, key):
        print("del key:", key)
        del self.gradeBook[key]

    def __iter__(self):
        yield from self.gradeBook.items()

    def __repr__(self):
        return "".join(f"{i}" for i in self.gradeBook.items())


print("="*10, "测试Student类")
gb = Gradebook()
bob = Student(100, 80, 90)
mary = Student(math=76, english=95)

print(mary)
mary.english = 88
mary.set_score("chinese", 35)
print(mary.chinese)

print("="*10, "测试Gradebook类")
# 动态添加学生
gb["bob"] = bob
gb["mary"] = mary
gb["tom"] = Student(english=88, chinese=69)

print(gb)
mary.set_score("english", 75)
print(gb.english)   # 显示所有学生英语成绩
with gb as names:
    for name in names:
        print(f"Name: {name:6} Score:[{gb[name]}]")

print("="*10, "修改分数")
gb["mary"].set_score("chinese", 36)
gb["mary"].set_score("math", 56)
gb["mary"].set_score("english", 77)
print(gb["mary"].sum, gb["mary"].avg)

print("="*10, "按条件获取")
best = ["default", default]
for name, info in gb:
    if info.avg > 80:  # 获取平均分大于80的学生
        print(name, info.avg)
    if info.sum > best[1].sum:    # 获取总分第一名
        best = name, info

print(best)

# 写的很棒，几乎把面向对象中的知识，全部用了一遍，赞~
